using System;

using Microsoft.SPOT;

using mfLogger;

using OST.Shared.SharedTypes;

namespace OST.Motor
{
    /// <summary>
    /// static class with runtime-calculation-methods for time-positioning
    /// </summary>
    public static class MotorCalculator
    {
        #region Properties

        /// <summary>
        /// wrapper-property 4 logging
        /// </summary>
        private static ILoggerAppender Logger
        {
            get
            {
                return mfLogger.mfLogger.Instance;
            }
        }
        #endregion

        #region Methods// Extensions

        /// <summary>
        /// calculates the overrun-error, based on the number of stops
        /// </summary>
        /// <param name="stops">number of stops</param>
        /// <param name="exceedThreshold">overrun-error threshold, after the number is exceeded the 'motorOverrunError' is added to position</param>
        /// <param name="motorOverrunError">overrun-error, in mm</param>
        /// <returns>relative overrun-error in mm</returns>
        public static int CalculateOverrunError(int stops, int exceedThreshold, int motorOverrunError)
        {
            if ((stops % exceedThreshold) == 0)
            {
                Logger.Debug("Overrun " + motorOverrunError + " mm calculated");
                return motorOverrunError;
            }
            else
                return 0;
        }

        /// <summary>
        /// calculates the overrun-runtime based on the overrun-error (in mm) and the motor-timeway-coefficient
        /// </summary>
        /// <param name="overrunError">overrun-error, in mm</param>
        /// <param name="timeWayCoefficient">motor-timeway-coefficient</param>
        /// <returns>overrun-runtime, in ms</returns>
        public static int CalculateOverrunRuntimeError(int overrunError, int timeWayCoefficient)
        {
            var motorOverrunErrorMS = (int)(overrunError * timeWayCoefficient);

            return motorOverrunErrorMS;
        }

        /// <summary>
        /// transforms the specified way (in mm) to motor-runtime (in ms)
        /// </summary>
        /// <param name="way">in mm</param>
        /// <param name="timeWayCoefficient">motor-timeway-coefficient</param>
        /// <returns>motor-runtime, in ms</returns>
        public static int TransformWay2Runtime(int way, int timeWayCoefficient)
        {
            return way * timeWayCoefficient;
        }

        /// <summary>
        /// transforms the motor-runtime (in ms) to specified way (in mm)
        /// </summary>
        /// <param name="runtime">in ms</param>
        /// <param name="timeWayCoefficient">motor-timeway-coefficient</param>
        /// <returns>way, in mm</returns>
        public static int TransformRuntime2Way(int runtime, int timeWayCoefficient)
        {
            return runtime / timeWayCoefficient;
        }

        /// <summary>
        /// calculates the runtime for a specified target position, depending on current position, driving Direction and the number of stops
        /// </summary>
        /// <param name="motorTarget">desired position, in mm</param>
        /// <param name="motorPos">current position, in mm</param>
        /// <param name="timeWayCoefficient">motor-timeway-coefficient</param>
        /// <param name="drivingDirection">current motor-driving-direction, <see cref="enumMotorDrivingDirection"/></param>
        /// <param name="exceedThreshold">overrun-error threshold, after the number is exceeded the 'motorOverrunError' is added to position, default-value = 1</param>
        /// <param name="motorOverrunError">overrun-error, in mm, default-value = 0</param>
        /// <param name="stops">absolute number of stops, in current drive, default-value = 0</param>
        /// <returns>runtime in ms</returns>
        public static int CalculateRuntime4TargetPosition(int motorTarget
                                                        , int motorPos
                                                        , int timeWayCoefficient
                                                        , enumMotorDrivingDirection drivingDirection
                                                        , int exceedThreshold = 1
                                                        , int motorOverrunError = 0
                                                        , int stops = 0)
        {
            var overrunErrorRuntime = CalculateOverrunRuntimeError(CalculateOverrunError(stops + 1, exceedThreshold, motorOverrunError), timeWayCoefficient);

            var curPosRuntime = TransformWay2Runtime(motorPos, timeWayCoefficient);
            var finalRuntime = TransformWay2Runtime(motorTarget, timeWayCoefficient) - curPosRuntime;

            if (drivingDirection == enumMotorDrivingDirection.MovingUp)
            {
                finalRuntime -= overrunErrorRuntime;
            }
            else if (drivingDirection == enumMotorDrivingDirection.MovingUp)
            {
                finalRuntime += overrunErrorRuntime;
            }

            finalRuntime = System.Math.Abs(finalRuntime);

            Logger.Info("CalculateRuntime4TargetPosition 4 " + motorTarget + " mm calculated with " + finalRuntime + " ms");

            return finalRuntime;
        }

        /// <summary>
        /// calculates the current motor stroke based on the actual drive-runtime (and the stops)
        /// </summary>
        /// <param name="currentMotorPosition">current motor-position, in mm</param>
        /// <param name="runtimeInMilliseconds">motor-runtime, after last calculation, in ms</param>
        /// <param name="drivingDirection">current motor-driving-direction, <see cref="enumMotorDrivingDirection"/></param>
        /// <param name="timeWayCoefficient">motor-timeway-coefficient</param>
        /// <param name="motorStroke">maximum motor-stroke, from configuration</param>
        /// <param name="exceedThreshold">overrun-error threshold, after the number is exceeded the 'motorOverrunError' is added to position, default-value = 1</param>
        /// <param name="motorOverrunError">overrun-error, in mm, default-value = 0</param>
        /// <param name="stops">absolute number of stops, in current drive, default-value = 0</param>
        /// <returns>new-position, in mm</returns>
        public static int CalculateMotorPosition(int currentMotorPosition
                                                , int runtimeInMilliseconds
                                                , enumMotorDrivingDirection drivingDirection
                                                , int timeWayCoefficient
                                                , int motorStroke
                                                , int exceedThreshold = 1
                                                , int motorOverrunError = 0
                                                , int stops = 0)
        {
            Logger.Debug("CalculateMotorPosition 4 " + runtimeInMilliseconds + " ms started");

            int newMotorPosition = currentMotorPosition;
            int motorOverrunErrorMM = CalculateOverrunError(stops + 1, exceedThreshold, motorOverrunError);

            if (drivingDirection == enumMotorDrivingDirection.EndDown)
            {
                newMotorPosition = 0;
            }
            else if (drivingDirection == enumMotorDrivingDirection.EndUp)
            {
                newMotorPosition = motorStroke;
            }
            else if (drivingDirection == enumMotorDrivingDirection.MovingUp)
            {
                newMotorPosition += TransformRuntime2Way(runtimeInMilliseconds, timeWayCoefficient) + motorOverrunErrorMM;
            }
            else if (drivingDirection == enumMotorDrivingDirection.MovingDown)
            {
                newMotorPosition -= TransformRuntime2Way(runtimeInMilliseconds, timeWayCoefficient) - motorOverrunErrorMM;
            }
            else
            {
                Logger.Warning("CalculateMotorPosition: no valid engineState --> new-position=-1");
                newMotorPosition = -1;
            }

            Logger.Debug("CalculateMotorPosition-result: " + newMotorPosition + "mm");

            return newMotorPosition;
        } 
        #endregion
    }
}
